﻿#include <iostream>
#include <learn/ais/camera.h>

using namespace xi;
using namespace xi::math;
using namespace xi::ais;

Camera::Camera(float width, float height, float distance, float pitch, float yaw)
    : m_center(0.0f, 0.0f, 0.0f), m_worldUp(0.0f, 0.0f, 1.0f), m_distance(distance), m_pitch(pitch), m_yaw(yaw)
{
    resize(width, height);
    _update_camera_vectors();
}

Mat4x4<float> Camera::view() const
{
    return m_viewMat;
}

Mat4x4<float> Camera::projection() const
{
    return m_projMat;
}

float Camera::yaw() const
{
    return m_yaw;
}

float Camera::pitch() const
{
    return m_pitch;
}

float Camera::distance() const
{
    return m_distance;
}

Vec3f Camera::front() const
{
    return m_front;
}

Vec3f Camera::position() const
{
    return m_center + m_front * m_distance;
}

Vec3f Camera::emit(Vec2f pos) const
{
    return (position() - space(pos)).normalize();
}

void Camera::resize(float width, float height)
{
    m_width = width;
    m_height = height;
    m_projMat = perspective<float>(45.0f, m_width / m_height, 0.1f, 1000.0f);
}

Vec3f Camera::space(Vec2f pos) const
{
    // 计算屏幕点对应的空间点
    float x = pos.x() * 2.0f / m_width - 1.0f;
    float y = -pos.y() * 2.0f / m_height + 1.0f;

    // z 坐标越接近 1，得到的点越远离相机，反之越接近相机
    Vec4f v{x, y, 0.8f, 1.0f};
    v = invert(m_projMat * m_viewMat) * v;
    return homogenous_to_cartesian(v);
}

Vec3f Camera::screen(Vec3f pos) const
{
    Vec4f p{pos.x(), pos.y(), pos.z(), 1.0f};
    Vec4f q = m_projMat * m_viewMat * p;
    Vec3f r;

    // 这个时候非常接近屏幕，将深度设为 2
    if (std::abs(q.w()) < std::numeric_limits<float>::epsilon())
        r.z() = 2.0f;
    else
    {
        q /= q.w();

        r.x() = (q.x() + 1.0f) * m_width / 2.0f;
        r.y() = -(q.y() - 1.0f) * m_height / 2.0f;
        r.z() = q.z();
    }
    return r;
}

void Camera::set_center(Vec3f center)
{
    m_center = center;
    _update_camera_vectors();
}

void Camera::set_pitch(float pitch)
{
    // 设置角度范围
    if (pitch < 0)
        pitch += 360.0f;
    if (pitch > 360.f)
        pitch -= 360.0f;

    // 根据角度设置世界方向
    int n = static_cast<int>(pitch / 180.f);
    if (n % 2 == 0)
        m_worldUp = Vec3f{0.0f, 0.0f, 1.0f};
    else
        m_worldUp = Vec3f{0.0f, 0.0f, -1.0f};

    m_pitch = pitch;
    _update_camera_vectors();
}

void Camera::set_yaw(float yaw)
{
    m_yaw = yaw;
    _update_camera_vectors();
}

void Camera::scale(float ratio)
{
    m_distance *= ratio;
    _update_camera_vectors();
}

void Camera::translate(Vec2f screen, float d)
{
    Vec3f v = emit(screen);
    m_center += v * d;
    _update_camera_vectors();
}

void Camera::translate(Vec2f dv)
{
    // 由于透视投影，无法确保所有点都平移相同距离，只保证原点的移动
    Vec3f p = space(Vec2f{dv.x() + m_width / 2, dv.y() + m_height / 2});
    Vec3f n = (position() - p).normalize();
    m_center = p + n * dot(m_center - p, n);
    _update_camera_vectors();
}

void Camera::rotate(float dyaw, float dpitch)
{
    set_yaw(m_yaw + dyaw);
    set_pitch(m_pitch + dpitch);
}

void Camera::rotate_front(Vec3f df)
{
    Vec3f pos = position();

    m_front = (m_front - df).normalize();
    m_center = pos - m_front * m_distance;

    // 计算欧拉角
    float rpitch = std::acos(m_front.z());
    float ryaw = std::acos(std::max(-1.0f, std::min(1.0f, m_front.x() / sin(rpitch))));

    set_pitch(radians_to_degrees(rpitch));
    set_yaw(radians_to_degrees(ryaw));

    _update_camera_vectors();
}

void Camera::upright_view()
{
    m_distance = position().modulus();
    m_worldUp = Vec3f{0.0f, 0.0f, 1.0f};
    m_pitch = 90.0f;
    m_yaw = 0.0f;
    m_center = Vec3f{0.0f, 0.0f, 0.0f};
    _update_camera_vectors();
}

void Camera::right_view()
{
    m_distance = position().modulus();
    m_worldUp = Vec3f{0.0f, 0.0f, 1.0f};
    m_pitch = 90.0f;
    m_yaw = 90.0f;
    m_center = Vec3f{0.0f, 0.0f, 0.0f};
    _update_camera_vectors();
}

void Camera::top_view()
{
    m_distance = position().modulus();
    m_worldUp = Vec3f{0.0f, 1.0f, 0.0f};
    m_pitch = 0.0f;
    m_yaw = 270.0f;
    m_center = Vec3f{0.0f, 0.0f, 0.0f};
    _update_camera_vectors();
}

void Camera::bottom_view()
{
    m_distance = position().modulus();
    m_worldUp = Vec3f{0.0f, 1.0f, 0.0f};
    m_pitch = 180.0f;
    m_yaw = 90.0f;
    m_center = Vec3f{0.0f, 0.0f, 0.0f};
    _update_camera_vectors();
}

void Camera::_update_camera_vectors()
{
    float ryaw = degrees_to_radians(m_yaw);
    float rpitch = degrees_to_radians(m_pitch);

    // 先算出方向，然后规范化
    m_front = Vec3f(cos(ryaw) * sin(rpitch), sin(ryaw) * sin(rpitch), cos(rpitch)).normalize();

    // 然后叉乘计算另外两个方向
    m_right = cross(m_front, m_worldUp);
    if (m_right.modulus() < std::numeric_limits<float>::epsilon())
    {
        m_worldUp = Vec3f{0.0f, 1.0f, 0.0f};
        m_right = cross(m_front, m_worldUp);
    }
    m_right.normalize();
    m_up = cross(m_right, m_front).normalize();

    m_viewMat = look_at<float>(position(), m_center, m_up);
}
